/*
 * @Author: Liang Huimin
 * @LastEditors: Liang Huimin
 * @email: lhm@cxehr.com
 * @Date: 2024-06-05 16:23:40
 * @LastEditTime: 2024-06-17 15:25:55
 * @motto: Still water run deep
 * @Description: Modify here please
 * @FilePath: \silky-deploy-service\lib\commands\upload.js
 */
const fs = require('fs')
const ora = require('ora')
const dayjs = require('dayjs')
const inquirer = require('inquirer')
const archiver = require('archiver')
const { NodeSSH } = require('node-ssh')
const childProcess = require('child_process')
const { deployConfigPath } = require('../config')
const {
  checkDeployConfigExists,
  log,
  succeed,
  error,
  underline
} = require('../utils')

const ssh = new NodeSSH()
const maxBuffer = 5000 * 1024

// 任务列表
let taskList
/**
 * 确认信息
 * @param {*} 信息内容
 * @returns
 */
const confirmDeploy = (message) => {
  return inquirer.prompt([
    {
      type: 'confirm',
      name: 'confirm',
      message
    }
  ])
}
/**
 * 检查环境是否正确
 * @param {*} config
 * @param {*} env
 */
const checkEnvCorrect = (config, env) => {
  const keys = [
    'name',
    'host',
    'port',
    'username',
    'distPath',
    'uploadDir',
  ]

  if (config) {
    keys.forEach((key) => {
      console.log(key)
      if (!config[env][key] || config[env][key] === '/') {
        error(
          `配置错误: ${underline(`${env}环境`)} ${underline(
            `${key}属性`
          )} 配置不正确`
        )
        process.exit(1)
      }
    })
  } else {
    error('配置错误: 未指定部署环境或指定部署环境不存在')
    process.exit(1)
  }
}
/**
 * 执行打包脚本
 * @param {*} config
 * @param {*} index
 */
const execBuild = async (config, index) => {
  try {
    const { script } = config
    log(`(${index}) ${script}`)
    const spinner = ora('正在打包中\n')

    spinner.start()

    await new Promise((resolve, reject) => {
      childProcess.exec(
        script,
        { cwd: process.cwd(), maxBuffer: maxBuffer },
        (e) => {
          spinner.stop()
          if (e === null) {
            succeed('打包成功')
            resolve()
          } else {
            reject(e.message)
          }
        }
      )
    })
  } catch (e) {
    error('打包失败')
    error(e)
    process.exit(1)
  }
}
/**
 * 打包文件为Zip
 * @param {*} config
 * @param {*} index
 */
const buildZip = async (config, index) => {
  await new Promise((resolve, reject) => {
    log(`(${index}) 打包 ${underline(config.distPath)} Zip`)
    const archive = archiver('zip', {
      zlib: { level: 9 },
      forceLocalTime: true
    }).on('error', (e) => {
      error(e)
    })
    const tempDir = `${process.cwd()}/deployTemp`
    if (!fs.existsSync(tempDir)) {
      fs.mkdirSync(tempDir)
      log(`(${index}) 创建临时文件夹成功`)
    }
    let zipFilePath = `${tempDir}/${config.fileName}`
    if (fs.existsSync(zipFilePath)) {
      fs.unlink(zipFilePath, (err) => {
        if (err) {
          error(`(${index}) 删除临时zip文件出错: ${err}`)
        } else {
          log(`(${index}) 删除临时zip文件成功`)
        }
      })
    }
    const output = fs
      .createWriteStream(zipFilePath)
      .on('close', (e) => {
        if (e) {
          error(`打包zip出错: ${e}`)
          reject(e)
          process.exit(1)
        } else {
          succeed(`${underline(`${config.distPath}.zip`)} 打包成功`)
          resolve()
        }
      })

    archive.pipe(output)
    archive.directory(config.distPath, false)
    archive.finalize()
  })
}
/**
 * 连接ssh
 * @param {*} config
 * @param {*} index
 */
const connectSSH = async (config, index) => {
  try {
    log(`(${index}) ssh连接 ${underline(config.host)}`)

    const { privateKey, passphrase, password } = config
    if (!privateKey && !password) {
      const answers = await inquirer.prompt([
        {
          type: 'password',
          name: 'password',
          message: '请输入服务器密码'
        }
      ])

      config.password = answers.password
    }

    !privateKey && delete config.privateKey
    !passphrase && delete config.passphrase

    await ssh.connect(config)
    succeed('ssh连接成功')
  } catch (e) {
    error(e)
    process.exit(1)
  }
}
/**
 * 上传本地文件
 * @param {*} config
 * @param {*} index
 */
const uploadLocalFile = async (config, index) => {
  try {
    const remoteFileName = `${config.uploadDir}/${config.fileName}`
    const tempDir = `${process.cwd()}/deployTemp`
    let zipFilePath = `${tempDir}/${config.fileName}`
    log(`(${index}) 上传打包zip至目录 ${underline(remoteFileName)}`)

    const spinner = ora('正在上传中\n')

    spinner.start()
    await ssh.execCommand(`del /S /Q ${remoteFileName}`)
    await ssh.putFile(zipFilePath, remoteFileName, null, {
      concurrency: 1
    })

    spinner.stop()
    succeed('上传成功')
  } catch (e) {
    error(`上传失败: ${e}`)
    process.exit(1)
  }
}

/**
 * 断开ssh
 */
const disconnectSSH = () => {
  ssh.dispose()
}

const httpDeploy = async (config, index) => {
  return new Promise((resolve, reject) => {
    // console.log(zipFilePath);
    // return;
    /* 使用formdata是为了模仿前端的formdata发送表单数据的方式上传文件。 */
    const form = new FormData({ maxDataSize: 1024 * 1024 * 100 })

    let url = `${config.type}://${config.host}`
    if (config.port) {
      url += `:${config.port}`
    }
    log(`(${index}) 开始上传部署到远程服务器 ${underline(url)}`)
    const zipFilePath = `${process.cwd()}/deployTemp/${config.fileName}`
    // const url = "http://localhost:8888/derlo-server/index.php/uploadfile"
    const fileobj = fs.createReadStream(zipFilePath)
    form.append('name', config.name)
    form.append('uploadDir', config.uploadDir)
    form.append('deployDir', config.deployDir)
    form.append('bakDir', config.bakDir)
    form.append('deployBeforeCmd', config.deployBeforeCmd)
    form.append('deployAfterCmd', config.deployAfterCmd)
    form.append('isRemoveRemoteFile', config.isRemoveRemoteFile.toString())
    form.append('isRemoveLocalFile', config.isRemoveLocalFile.toString())
    form.append('fileName', config.fileName)
    form.append('notCover', config.notCover)
    form.append('uploadname', fileobj)
    const progressEventFun = (e) => {
      console.log('progressEventFun', e)
    }
    axios
      .post(url + '/upload', form, {
        auth: {
          username: config.username,
          password: config.password
        },
        headers: form.getHeaders(),
        onUploadProgress: progressEventFun,
        maxContentLength: Infinity,
        maxBodyLength: Infinity
      })
      .then((res) => {
        console.log(res.data)
        log('上传成功！ohye~')
        resolve()
      })
      .catch((error) => {
        console.error(error.message)
        log('上传失败！')
        reject()
      })
  })
}
/**
 * 创建任务列表
 * @param {*} config
 */
const createTaskList = (config) => {
  const {
    script,
    bakDir,
    deployBeforeCmd,
    deployAfterCmd,
    isRemoveRemoteFile = true,
    isRemoveLocalFile = true
  } = config

  taskList = []
  script && taskList.push(execBuild)
  taskList.push(buildZip)
  taskList.push(connectSSH)
  taskList.push(uploadLocalFile)
  taskList.push(disconnectSSH)
}
/**
 * 执行任务列表
 * @param {*} config
 */
const executeTaskList = async (config) => {
  for (const [index, execute] of new Map(
    taskList.map((execute, index) => [index, execute])
  )) {
    await execute(config, index + 1)
  }
}

/**
 * 创建任务列表
 * @param {*} config
 */
const createHTTPTaskList = (config) => {
  const {
    script,
    bakDir,
    deployBeforeCmd,
    deployAfterCmd,
    isRemoveRemoteFile = true,
    isRemoveLocalFile = true
  } = config

  taskList = []
  script && taskList.push(execBuild)
  //taskList.push(buildZip)
  taskList.push(httpDeploy)
  isRemoveLocalFile && taskList.push(removeLocalFile)
}
/**
 * 入口函数
 */
module.exports = {
  description: '发布项目',
  apply: async (env) => {
    console.log('env', env)
    if (checkDeployConfigExists()) {
      const config = require(deployConfigPath)
      const cluster = config.cluster
      const projectName = config.projectName
      const currentTime = new Date().getTime()

      /**
       * 创建环境配置
       * @param {*} env
       * @returns
       */
      const createdEnvConfig = (env) => {
        checkEnvCorrect(config, env)

        return Object.assign(config[env], {
          privateKey: config.privateKey,
          passphrase: config.passphrase,
          readyTimeout: config.readyTimeout,
          fileName: `${projectName}_${env}.zip`
        })
      }

      if (env) {
        const envConfig = createdEnvConfig(env)

        const answers = await confirmDeploy(
          `${underline(projectName)} 项目是否发布到 ${underline(
            envConfig.name
          )}?`
        )

        if (answers.confirm) {
          if (envConfig.type === 'HTTP') {
            createHTTPTaskList(envConfig)

            await executeTaskList(envConfig)

            succeed(
              `恭喜您，${underline(projectName)}项目已在${underline(
                envConfig.name
              )}发布成功 耗时${(new Date().getTime() - currentTime) / 1000}s\n`
            )
            process.exit(1)
          } else {
            createTaskList(envConfig)

            await executeTaskList(envConfig)

            succeed(
              `恭喜您，${underline(projectName)}项目已在${underline(
                envConfig.name
              )}发布成功 耗时${(new Date().getTime() - currentTime) / 1000}s\n`
            )
            process.exit(0)
          }
        } else {
          process.exit(1)
        }
      } else if (cluster && cluster.length > 0) {
        const answers = await confirmDeploy(
          `${underline(projectName)} 项目是否发布到 ${underline('集群环境')}?`
        )

        if (answers.confirm) {
          for (const env of cluster) {
            const envConfig = createdEnvConfig(env)

            if (envConfig.type === 'HTTP') {
              createHTTPTaskList(envConfig)

              await executeTaskList(envConfig)

              succeed(
                `恭喜您，${underline(projectName)}项目已在${underline(
                  envConfig.name
                )}发布成功 耗时${
                  (new Date().getTime() - currentTime) / 1000
                }s\n`
              )
              process.exit(1)
            } else {
              createTaskList(envConfig)

              await executeTaskList(envConfig)

              succeed(
                `恭喜您，${underline(projectName)}项目已在${underline(
                  envConfig.name
                )}发布成功 耗时${
                  (new Date().getTime() - currentTime) / 1000
                }s\n`
              )
              process.exit(0)
            }
          }

          succeed(
            `恭喜您，${underline(projectName)}项目已在${underline(
              '集群环境'
            )}发布成功 耗时${(new Date().getTime() - currentTime) / 1000}s\n`
          )
        } else {
          process.exit(1)
        }
      } else {
        error(
          '请使用 deploy-cli-service -mode 指定部署环境或在配置文件中指定 cluster（集群）地址'
        )
        process.exit(1)
      }
    } else {
      error(
        'deploy.config.js 文件不存，请使用 deploy-cli-service init 命令创建'
      )
      process.exit(1)
    }
  }
}
